﻿using System;

using Microsoft.Practices.ServiceLocation;
using System.Collections.Generic;

namespace Fuse8.DomainFramework.Common.Locator
{
    internal class LazyInitLocatorActionInvoker
    {
        public bool YetRun { get; set; }

        public LazyInitLocatorAction Action { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    public abstract class ServiceLocatorBaseEx : ServiceLocatorImplBase, IServiceLocatorWithLazy
    {
        #region Fields

        private static object _syncRoot = new object();

        private Dictionary<string, LazyInitLocatorActionInvoker> _initActions = new Dictionary<string, LazyInitLocatorActionInvoker>();

        #endregion

        #region Override methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public override object GetInstance(Type serviceType)
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");

            DoInitAction(serviceType.FullName);            

            return IsRegistered(serviceType) ?
                base.GetInstance(serviceType) :
                GetDefaultService(serviceType);
        }        

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public override object GetInstance(Type serviceType, string key)
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");

            DoInitAction(serviceType.FullName);

            return base.GetInstance(serviceType, key);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");

            DoAllInitActions();

            return base.GetAllInstances(serviceType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public override T GetInstance<T>()
        {
            DoInitAction(typeof(T).FullName);

            return IsRegistered<T>() ?
                base.GetInstance<T>() :
                GetDefaultService<T>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public override T GetInstance<T>(string key)
        {
            Guard.ArgumentNotEmpty(key, "key");

            DoInitAction(typeof(T).FullName);

            return base.GetInstance<T>(key);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public override IEnumerable<T> GetAllInstances<T>()
        {
            DoAllInitActions();

            return base.GetAllInstances<T>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public override object GetService(Type serviceType)
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");

            DoInitAction(serviceType.FullName);
            
            return base.GetService(serviceType);
        }

        #endregion

        #region IServiceLocatorWithLazy methods

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        public virtual void DefineInitAction<T>(LazyInitLocatorAction action)
        {
            if (action != null)
            {
                RegisterInitAction(typeof(T).FullName, action);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="action"></param>
        public virtual void DefineInitAction(Type type, LazyInitLocatorAction action)
        {
            if ((type != null) && (action != null))
            {
                RegisterInitAction(type.FullName, action);
            }
        }

        #endregion

        #region Private methods

        private void RegisterInitAction(string typeDef, LazyInitLocatorAction action)
        {
            lock (_syncRoot)
            {
                if (_initActions.ContainsKey(typeDef))
                {
                    _initActions[typeDef] = new LazyInitLocatorActionInvoker { Action = action };
                }
                else
                {
                    _initActions.Add(typeDef, new LazyInitLocatorActionInvoker { Action = action });
                }
            }
        }

        private void DoInitAction(string typeDef)
        {
            lock (_syncRoot)
            {
                if (_initActions.ContainsKey(typeDef))
                {
                    var invoker = _initActions[typeDef];

                    if (!invoker.YetRun)
                    {
                        invoker.Action();

                        invoker.YetRun = true;
                    }
                }
            }
        }

        private void DoAllInitActions()
        {
            lock (_syncRoot)
            {
                var enumerator = _initActions.GetEnumerator();

                while (enumerator.MoveNext())
                {
                    var invoker = enumerator.Current.Value;

                    if (!invoker.YetRun)
                    {
                        invoker.Action();

                        invoker.YetRun = true;
                    }
                }
            }
        }

        #endregion

        #region IServiceLocatorEx Members

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="service"></param>
        public abstract void Register<TService>(TService service);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="key"></param>
        /// <param name="service"></param>
        public abstract void Register<TService>(string key, TService service);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="typeFrom"></param>
        /// <param name="serviceType"></param>
        public abstract void Register(Type typeFrom, Type serviceType);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="typeTo"></param>
        /// <param name="serviceType"></param>
        /// <param name="key"></param>
        public abstract void Register(Type typeTo, Type serviceType, string key);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <returns></returns>
        public abstract bool IsRegistered<TService>();

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public abstract bool IsRegistered<TService>(string key);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public abstract bool IsRegistered(Type serviceType);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public abstract bool IsRegistered(Type serviceType, string key);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="service"></param>
        public abstract void SetDefaultService<TService>(TService service);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        public abstract void SetDefaultService(Type serviceType);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <returns></returns>
        public abstract TService GetDefaultService<TService>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        public abstract object GetDefaultService(Type serviceType);

        /// <summary>
        /// Check if an instance of TService is registered in IoC as default service
        /// </summary>
        /// <typeparam name="TService">Type of instance</typeparam>
        /// <returns>True if registered, false if not</returns>
        public abstract bool IsDefaultServiceRegistered<TService>();

        /// <summary>
        /// Check if a type is registered in IoC as a type for default service
        /// </summary>
        /// <param name="serviceType">Type of registration</param>
        /// <returns>True if registered, false if not</returns>
        public abstract bool IsDefaultServiceRegistered(Type serviceType);

        #endregion
    }
}
